/* 
 * File:   XMLSceneBuilder.cpp
 * Author: brady
 * 
 * Created on August 16, 2011, 7:52 PM
 */

#include <lights/AreaLight.h>


#include <parsing/XMLSceneBuilder.h>
#include <parsing/ObjParser.h>
#include <acceleration/BoundingVolumeHierarchy.h>
#include <acceleration/VoxelGrid.h>


XMLSceneBuilder::XMLSceneBuilder() {
}

XMLSceneBuilder::XMLSceneBuilder(const XMLSceneBuilder& orig) {
}

XMLSceneBuilder::~XMLSceneBuilder() {
}

Scene* XMLSceneBuilder::buildSceneFromFile(const char* inputFile){
    //initialize a new scene
    Scene* scene = new Scene();

    if(gParams.acceleration == "bvh" || gParams.acceleration == "BVH")
        scene->intersectionStructure = new BoundingVolumeHierarchy();

    else if(gParams.acceleration == "vg" || gParams.acceleration == "VG")
        scene->intersectionStructure =  new VoxelGrid();

    else if(gParams.acceleration == "sl" || gParams.acceleration == "SL")
        scene->intersectionStructure = new ShapeList();

    else
        scene->intersectionStructure = new ShapeList();
 
    xmlDocPtr doc;
    xmlNodePtr cur;
    doc = xmlParseFile(inputFile);

    if(doc == NULL){
        cout << "Failed to read scene file " << inputFile <<"\n";
    }

    cur = xmlDocGetRootElement(doc);

    if(cur == NULL){
        cout << "Scene file is empty\n";
    }

    if(xmlStrcmp(cur->name, (xmlChar*)"scene") != 0){
        cout << "Root node of scene file must be of type <scene>";
    }

    //begin parsing from inside the root
    cur = cur->children;

    while(cur != NULL){
        int typeId = -1;

        const xmlChar* nodeType = cur->name;

        if(xmlStrcmp(nodeType, (xmlChar*)"text")== 0)       typeId = 0;
        if(xmlStrcmp(nodeType, (xmlChar*)"shape")== 0)      typeId = 1;
        if(xmlStrcmp(nodeType, (xmlChar*)"camera")== 0)     typeId = 2;
        if(xmlStrcmp(nodeType, (xmlChar*)"light")== 0)      typeId = 3;
        if(xmlStrcmp(nodeType, (xmlChar*)"background")== 0) typeId = 4;
        if(xmlStrcmp(nodeType, (xmlChar*)"ambient")== 0)    typeId = 5;
        if(xmlStrcmp(nodeType, (xmlChar*)"comment") == 0)   typeId = 6;
        switch(typeId) {
            case 0: break;
            case 1: {
                
                Shape* shape = parseShape(doc, cur);
                
                xmlChar* visibleStr = xmlGetProp(cur, (xmlChar*)"visible");
                bool visible = (visibleStr == NULL || !xmlStrcmp(visibleStr, (xmlChar*)"false") == 0);
                if(visible) scene->intersectionStructure->addShape(shape);
                break;
            }
            case 2: {
                scene->camera = *parseCamera(doc, cur);
                break;
            }
            case 3: {
                scene->lights.push_back(parseLight(doc, cur));
                break;
            }
            case 4: {
                scene->backgroundColor = parseBackground(doc, cur);
                break;
            }
            case 5: {
                scene->ambientColor = parseAmbientLight(doc, cur);
                break;
            }
            case 6: {
                break;
            }
            default:
                cout << "Invalid element: '" << (char*)nodeType << "'\n";
        }

        //go to the next node
        cur = cur->next;
    }

    scene->intersectionStructure->finalize();
    return scene;
}

Camera* XMLSceneBuilder::parseCamera(xmlDocPtr doc, xmlNodePtr cur){
    xmlNodePtr start = cur;    
    xmlChar* focalLengthStr = xmlGetProp(cur, (xmlChar*)"focalLength");
    if(focalLengthStr == NULL){
        cout << "Camera element missing 'focalLength' property\n";
        xmlFree(focalLengthStr);
        return NULL;
    }
    double focalLength = atof((char*)focalLengthStr);
    xmlFree(focalLengthStr);
    
    xmlChar* imageWidthStr = xmlGetProp(cur, (xmlChar*)"imageWidth");
    if(imageWidthStr == NULL){
        xmlFree(imageWidthStr);
        cout << "Camera element missing 'imageWidth' property\n";
        return NULL;
    }
    double imageWidth = atof((char*)imageWidthStr);
    xmlFree(imageWidthStr);

    double lenseSize = 0.0;
    xmlChar* lenseSizeStr = xmlGetProp(cur, (xmlChar*)"lenseSize");
    if(lenseSizeStr != NULL){
        lenseSize = atof((char*)lenseSizeStr);
    }
    xmlFree(lenseSizeStr);
    
    Vector3D gaze(0,0,0);
    bool foundGaze = false;
    Vector3D origin(0,0,0);
    bool foundOrigin = false;
    xmlChar* gazeType;
    cur = cur->children;
    while(cur != NULL){
        if(xmlStrcmp(cur->name, (xmlChar*)"origin") == 0){
            foundOrigin = true;
            origin = parseVector(doc, cur);
        }
        if(xmlStrcmp(cur->name, (xmlChar*)"gaze") == 0){
            foundGaze = true;
            gazeType = xmlGetProp(cur, (xmlChar*)"type");
            gaze = parseVector(doc, cur);
        }
        cur = cur->next;
    }
    
    if(!foundOrigin){
        cout << "Camera element missing origin\n";
        xmlFree(gazeType);
        return NULL;
    }
    if(!foundGaze){
        cout << "Camera element missing gaze\n";
        xmlFree(gazeType);
        return NULL;
    }
    
    if((gazeType != NULL) && (xmlStrcmp(gazeType, (xmlChar*)"target") == 0)){
        gaze -= origin;
    }
    gaze.normalize();
    Camera* camera = new Camera();
    camera->focalLength = focalLength;
    camera->planeWidth = imageWidth;
    camera->lenseSize = lenseSize;
    camera->gaze = gaze;
    camera->origin = origin;
    camera->buildBasis();
    
    xmlFree(gazeType);
    cur = start;

    return camera; 
}

Vector3D XMLSceneBuilder::parseAmbientLight(xmlDocPtr doc, xmlNodePtr cur){
    xmlNodePtr tmp = cur;
    tmp = tmp->children;
    while(tmp != NULL){
        if(xmlStrcmp(tmp->name, (xmlChar*)"color") == 0){
            return parseVector(doc, tmp);
        }
        tmp = tmp->next;
    }

    cout << "Ambient light element missing color\n";
    return Vector3D(0,0,0);
}

Light* XMLSceneBuilder::parseLight(xmlDocPtr doc, xmlNodePtr cur){
    xmlChar* lightType = xmlGetProp(cur, (xmlChar*)"type");
    if(lightType == NULL){
        cout << "Light element missing 'type' attribute\n";
        return NULL;
    }
    //point
    if(xmlStrcmp(lightType, (xmlChar*)"point") == 0){
        xmlFree(lightType);
        return parsePointLight(doc, cur);
    }
    //area
    if(xmlStrcmp(lightType, (xmlChar*)"area") == 0){
        xmlFree(lightType);
        return parseAreaLight(doc, cur);
    }
    
    //not a valid light type
    cout << "Invalid light type: " << (char*)lightType <<"\n";
    xmlFree(lightType);
    return NULL;
}

PointLight* XMLSceneBuilder::parsePointLight(xmlDocPtr doc, xmlNodePtr cur){
    xmlNodePtr start = cur;       
    Vector3D color(0,0,0);
    bool foundColor = false;
    Vector3D origin(0,0,0);
    bool foundOrigin = false;
    cur = cur->children;
    while(cur != NULL){
        if(xmlStrcmp(cur->name, (xmlChar*)"origin") == 0){
            foundOrigin = true;
            origin = parseVector(doc, cur);
        }
        if(xmlStrcmp(cur->name, (xmlChar*)"color") == 0){
            foundColor = true;
            color = parseVector(doc, cur);
        }
        cur = cur->next;
    }
    
    if(!foundOrigin){
        cout << "Point light element missing origin\n";
        return NULL;
    }
    if(!foundColor){
        cout << "Point light element missing color\n";
        return NULL;
    }
    
    PointLight* light = new PointLight(origin, color);
    cur = start;
    return light;
}

AreaLight* XMLSceneBuilder::parseAreaLight(xmlDocPtr doc, xmlNodePtr cur){
    xmlNodePtr start = cur;

    xmlChar* sizeStr = xmlGetProp(cur, (xmlChar*)"size");
    if(sizeStr == NULL){
        cout << "Area light element missing 'size' property\n";
        xmlFree(sizeStr);
        return NULL;
    }
    double size = atof((char*)sizeStr);
    xmlFree(sizeStr);

    Vector3D color(0,0,0);
    bool foundColor = false;
    Vector3D origin(0,0,0);
    bool foundOrigin = false;
    cur = cur->children;
    while(cur != NULL){
        if(xmlStrcmp(cur->name, (xmlChar*)"origin") == 0){
            foundOrigin = true;
            origin = parseVector(doc, cur);
        }
        if(xmlStrcmp(cur->name, (xmlChar*)"color") == 0){
            foundColor = true;
            color = parseVector(doc, cur);
        }
        cur = cur->next;
    }

    if(!foundOrigin){
        cout << "Area light element missing origin\n";
        return NULL;
    }
    if(!foundColor){
        cout << "Area light element missing color\n";
        return NULL;
    }

    AreaLight* light = new AreaLight(origin, color);
    light->setSize(size);
    cur = start;
    return light;
}

Vector3D XMLSceneBuilder::parseBackground(xmlDocPtr doc, xmlNodePtr cur){
    xmlChar* backgroundType = xmlGetProp(cur, (xmlChar*)"type");
    if(backgroundType == NULL){
        cout << "Background element missing 'type' attribute\n";
        return Vector3D(0,0,0);
    }
    //solid
    if(xmlStrcmp(backgroundType, (xmlChar*)"solid") == 0){
        xmlFree(backgroundType);
        return parseSolidBackground(doc, cur);
    }

    //not a valid background type
    cout << "Invalid background type: " << (char*)backgroundType <<"\n";
    xmlFree(backgroundType);
    return Vector3D(0,0,0);
}

Vector3D XMLSceneBuilder::parseSolidBackground(xmlDocPtr doc, xmlNodePtr cur){
    xmlNodePtr start = cur;       
    Vector3D color(0,0,0);
    bool foundColor = false;
    cur = cur->children;
    while(cur != NULL){
        if(xmlStrcmp(cur->name, (xmlChar*)"color") == 0){
            foundColor = true;
            color = parseVector(doc, cur);
        }
        cur = cur->next;
    }
    
    if(!foundColor){
        cout << "Solid background element missing color\n";
        return Vector3D(0,0,0);
    }
    
    cur = start;
    return color;
}

Shape* XMLSceneBuilder::parseShape(xmlDocPtr doc, xmlNodePtr cur) {
    bool foundShader = false;
    xmlChar* refId = xmlGetProp(cur, (xmlChar*)"ref");
    if(refId != NULL)
    {
        //see if it's indexed already
        Shape* shape = getIndexedShape((char*)refId);
        if(shape != NULL){
            xmlFree(refId);
            return shape;
        }
        //if not, find it and parse it
        shape = parseShape(doc, getNodeById(doc, xmlDocGetRootElement(doc), refId));
        xmlFree(refId);
        return shape;
    }
    xmlFree(refId);

    xmlChar* shapeType = xmlGetProp(cur, (xmlChar*)"type");
    if(shapeType == NULL){
        cout << "Shape element missing 'type' attribute\n";
        return NULL;
    }
    
    Shape* shape = NULL;
    
    //Sphere
    if(xmlStrcmp(shapeType, (xmlChar*)"sphere") == 0){
        xmlFree(shapeType);
        shape = parseSphere(doc, cur);
    }
    //triangle
    else if(xmlStrcmp(shapeType, (xmlChar*)"triangle") == 0){
        xmlFree(shapeType);
        shape = parseTriangle(doc, cur);
    }
    //mesh
    else if(xmlStrcmp(shapeType, (xmlChar*)"mesh") == 0){
        xmlFree(shapeType);
        shape = parseMesh(doc, cur);
    } 
    else if(xmlStrcmp(shapeType, (xmlChar*)"instance") == 0){
        xmlFree(shapeType);
        shape = parseInstance(doc, cur);
    }
    else if(xmlStrcmp(shapeType, (xmlChar*)"box") == 0){
        xmlFree(shapeType);
        shape = parseBox(doc, cur);
    }
    else{
    //not a valid shape type
    cout << "Invalid Shape Type: " << (char*)shapeType <<"\n";
    xmlFree(shapeType);
    return NULL;
    }
    
    //index the shape if an ID exists
    if(shape != NULL){
        xmlChar* nodeId = xmlGetProp(cur, (xmlChar*)"id");
        if(nodeId != NULL && getIndexedShape((char*)nodeId) == NULL){
            stringstream ss;
            ss << (char*)nodeId;
            shape->name = ss.str();
            namedShapes.push_back(shape);
        }
        xmlFree(nodeId);
    }
    
    //extract shader  
    xmlNodePtr start = cur;
    cur = cur->children; //shape's first child
    while(cur != NULL){
        if(nodeNameEquals(cur, "shader")){
            shape->setShader(parseShader(doc, cur));
            foundShader = true;
        }
        cur = cur->next;
    }
    cur = start;
    
    if(!foundShader){
        cout << "Shape element missing shader\n";
        return NULL;
    }
    
    //extract texture
    bool foundTexture = false;
    start = cur;
    cur = cur->children;
    while(cur != NULL){
        if(nodeNameEquals(cur, "texture")) {
            shape->texture = parseTexture(doc, cur);
            foundTexture = true;
        }
        cur = cur->next;
    }
    if(!foundTexture){
        cout << "Shape element missing texture\n";
        return NULL;
    }
    cur = start;
    
    shape->finalize();
    return shape;
}

Sphere* XMLSceneBuilder::parseSphere(xmlDocPtr doc, xmlNodePtr cur){
    xmlNodePtr start = cur;
    bool foundOrigin = false;
    Vector3D origin(0,0,0);
    double radius;
    xmlChar* radiusStr = xmlGetProp(cur, (xmlChar*)"radius");
    if(radiusStr == NULL){
        cout << "Sphere element missing 'radius' attribute\n";
        return NULL;
    }
    else{
        radius = atof((char*)radiusStr);
    }
    
    cur = cur->children;
    while(cur != NULL){
        if(nodeNameEquals(cur, "origin")){
            origin = parseVector(doc, cur);
            foundOrigin = true;
        }
        cur = cur->next;
    }

    if(!foundOrigin){
        cout << "Sphere element missing origin\n";
        return NULL;
    }

    Sphere* sphere = new Sphere(origin, radius);

    //cleanup
    cur = start;
    return sphere;
}

Triangle* XMLSceneBuilder::parseTriangle(xmlDocPtr doc, xmlNodePtr cur){
    xmlNodePtr start = cur;
    int pointCount = 0;
    Vector3D points[3];

    cur = cur->children;
    while(cur != NULL){
        if(nodeNameEquals(cur, "point") && pointCount < 3){
            points[pointCount] = parseVector(doc, cur);
            pointCount++;
        }
        cur = cur->next;
    }

    if(pointCount < 3){
        cout << "Triangle element missing one or more points\n";
        return NULL;
    }

    Triangle* triangle = new Triangle(new Vertex(points[0]), 
                                      new Vertex(points[1]), 
                                      new Vertex(points[2]));

    //cleanup
    cur = start;
    return triangle;
}

TriangleMesh* XMLSceneBuilder::parseMesh(xmlDocPtr doc, xmlNodePtr cur){
    xmlChar* srcStr = xmlGetProp(cur, (xmlChar*)"src");
    //TriangleMesh* mesh = ObjParser::parseObj((char*)srcStr);
    vector<TriangleMesh*>mesh = ObjParser::parseObj2((char*)srcStr);
    if(mesh[0] == NULL){
        return NULL;
    }
    xmlFree(srcStr);
    return mesh[0];
}

Box* XMLSceneBuilder::parseBox(xmlDocPtr doc, xmlNodePtr cur){
    xmlNodePtr start = cur;
    bool foundOrigin = false;
    bool foundSize = false;
    Vector3D origin(0,0,0);
    Vector3D size(1,1,1);
    
    cur = cur->children;
    while(cur != NULL){
        if(nodeNameEquals(cur, "origin")){
            origin = parseVector(doc, cur);
            foundOrigin = true;
        }
        if(nodeNameEquals(cur, "size")){
            size = parseVector(doc, cur);
            foundSize = true;
        }
        cur = cur->next;
    }

    if(!foundOrigin){
        cout << "Box element missing origin\n";
        return NULL;
    }
    if(!foundSize){
        cout << "Box element missing size\n";
        return NULL;
    }
    
    Box* box = new Box(origin, size);
    //cleanup
    cur = start;
    return box;
}

Instance* XMLSceneBuilder::parseInstance(xmlDocPtr doc, xmlNodePtr cur){
    //parse the transforms inline
    xmlNodePtr start = cur;
    Shape* baseShape = NULL;
    xmlChar* baseShapeId = xmlGetProp(cur, (xmlChar*)"baseShape");
    if(baseShapeId != NULL)
    {
        //see if it's indexed already
        baseShape = (Shape*) getIndexedShape((char*) baseShapeId);
        if(baseShape == NULL){
            //if not, find it and parse it
            baseShape = parseShape(doc, getNodeById(doc, xmlDocGetRootElement(doc), baseShapeId));
        }
        xmlFree(baseShapeId);
    }
    else{
        cout << "Instance element missing 'baseShape' attribute\n";
        xmlFree(baseShapeId);
        return NULL;
    }
    if(baseShape == NULL){
        cout << "Instance element with undefined 'baseShape' reference\n";
        return NULL;
    }
    //now baseShape is defined
    Instance* instance = new Instance();
    instance->setShape(baseShape);

    while(cur != NULL){
        if(nodeNameEquals(cur, "transform")){
            xmlChar* transformType = xmlGetProp(cur, (xmlChar*)"type");
            if(transformType == NULL){
                cout << "Transform element missing 'type' attribute\n";
            }
            else{
                if(xmlStrcmp(transformType, (xmlChar*)"translate") == 0){
                    Vector3D offset = parseVector(doc, cur);
                    instance->translate(offset[0], offset[1], offset[2]);
                }
                if(xmlStrcmp(transformType, (xmlChar*)"rotate") == 0){
                    xmlChar* axis = xmlGetProp(cur, (xmlChar*)"axis");
                    xmlChar* degrees = xmlGetProp(cur, (xmlChar*)"degrees");
                    if(axis == NULL){
                        cout << "Rotation missing 'axis' property\n";
                    }
                    if(degrees == NULL){
                        cout << "Rotation missing 'degrees' property\n";
                    }
                    if(degrees != NULL && axis != NULL){
                        int axisId = -1;
                        if(xmlStrcmp(axis, (xmlChar*)"x") == 0){
                            axisId = TransformMatrix::X_AXIS;
                        }
                        else if(xmlStrcmp(axis, (xmlChar*)"y") == 0){
                            axisId = TransformMatrix::Y_AXIS;
                        }
                        else if(xmlStrcmp(axis, (xmlChar*)"z") == 0){
                            axisId = TransformMatrix::Z_AXIS;
                        }
                        if(axisId == -1){
                            cout << "invalid rotation axis: " << (char*)axis << "\n";
                        }
                        else{
                            double degs = atof((char*) degrees);
                            instance->rotate(degs, axisId);
                        }
                    }
                }
                if(xmlStrcmp(transformType, (xmlChar*)"scale") == 0){
                    Vector3D scaleFactor = parseVector(doc, cur);
                    instance->scale(scaleFactor[0], scaleFactor[1], scaleFactor[2]);
                }
            }
            xmlFree(transformType);
        }
        cur = cur->next;
    }
    //now all transforms have been applied
    cur = start;
    return instance;
}

Shader* XMLSceneBuilder::parseShader(xmlDocPtr doc, xmlNodePtr cur){
    xmlChar* refId = xmlGetProp(cur, (xmlChar*)"ref");
    if(refId != NULL)
    {
        //check if it's been stored
        Shader* shader = getIndexedShader((char*)refId);
        if(shader != NULL){
            xmlFree(refId);
            return shader;
        }
        //if not, find it and parse it
        shader = parseShader(doc, getNodeById(doc, xmlDocGetRootElement(doc), refId));
        xmlFree(refId);
        return shader;
    }
    xmlFree(refId);

    xmlChar* shaderType = xmlGetProp(cur, (xmlChar*)"type");
    if(shaderType == NULL){
        cout << "Shader element missing 'type' property\n";
        return NULL;
    }
    
    
    Shader* shader;
    
    //lambertian
    if(xmlStrcmp(shaderType, (xmlChar*)"lambertian") == 0){
        xmlFree(shaderType);
        shader = parseLambertianShader(doc, cur);
    }
    else if(xmlStrcmp(shaderType, (xmlChar*) "blinnphong") == 0){
        xmlFree(shaderType);
        shader = parseBlinnphongShader(doc, cur);
    }
    //dielectric
    else if(xmlStrcmp(shaderType, (xmlChar*)"dielectric") == 0){
        xmlFree(shaderType);
        shader = parseDielectricShader(doc, cur);
    }
    else {
        cout << "Invalid shader type: '" << (char*)shaderType << "'\n";
        xmlFree(shaderType);
        return NULL;
    }
    //index the shader if an ID exists
    if(shader != NULL){
        xmlChar* nodeId = xmlGetProp(cur, (xmlChar*)"id");
        if(nodeId != NULL && getIndexedShader((char*)nodeId) == NULL){
            stringstream ss;
            ss << (char*)nodeId;
            shader->name = ss.str();
            namedShaders.push_back(shader);
        }
        xmlFree(nodeId);
    }
    
    return shader;
}

LambertianShader* XMLSceneBuilder::parseLambertianShader(xmlDocPtr doc, xmlNodePtr cur){
    LambertianShader* lambertianShader = new LambertianShader();
    return lambertianShader;
}

BlinnphongShader* XMLSceneBuilder::parseBlinnphongShader(xmlDocPtr doc, xmlNodePtr cur){
    Vector3D specularColor(0,0,0);
    bool foundSpecular = false;
    xmlNodePtr start = cur;

    double phongExponent;
    xmlChar* phongExpStr = xmlGetProp(cur, (xmlChar*)"phongExp");
    if(phongExpStr == NULL){
        cout << "Blinnphong shader element missing 'phongExp' attribute\n";
        xmlFree(phongExpStr);
        return NULL;
    }
    else{
        phongExponent = atof((char*)phongExpStr);
        xmlFree(phongExpStr);
    }

    double mirrorVal;
    xmlChar* mirrorStr = xmlGetProp(cur, (xmlChar*)"mirrorVal");
    if(mirrorStr == NULL){
        cout << "Blinnphong shader element missing 'mirrorVal' attribute\n";
        xmlFree(mirrorStr);
        return NULL;
    }
    else{
        mirrorVal = atof((char*)mirrorStr);
        xmlFree(mirrorStr);
    }

    cur = cur->children;
    while(cur != NULL){
        if(nodeNameEquals(cur, "color")) {
            specularColor = parseVector(doc, cur);
            foundSpecular = true;
        }
        cur = cur->next;
    }
    if(!foundSpecular){
        cout << "Blinnphong shader element missing specular color\n";
        return NULL;
    }

    BlinnphongShader* blinnphongShader = new BlinnphongShader();
    blinnphongShader->setSpecularColor(specularColor);
    blinnphongShader->setReflectivity(mirrorVal);
    blinnphongShader->setPhongExponent(phongExponent);
    cur = start;
    return blinnphongShader;
}

DielectricShader* XMLSceneBuilder::parseDielectricShader(xmlDocPtr doc, xmlNodePtr cur){
    Vector3D specularColor(0,0,0);
    bool foundSpecular = false;
    xmlNodePtr start = cur;

    double phongExponent;
    xmlChar* phongExpStr = xmlGetProp(cur, (xmlChar*)"phongExp");
    if(phongExpStr == NULL){
        cout << "Dielectric shader element missing 'phongExp' attribute\n";
        xmlFree(phongExpStr);
        return NULL;
    }
    else{
        phongExponent = atof((char*)phongExpStr);
        xmlFree(phongExpStr);
    }

    double mirrorVal;
    xmlChar* mirrorStr = xmlGetProp(cur, (xmlChar*)"mirrorVal");
    if(mirrorStr == NULL){
        cout << "Dielectric shader element missing 'mirrorVal' attribute\n";
        xmlFree(mirrorStr);
        return NULL;
    }
    else{
        mirrorVal = atof((char*)mirrorStr);
        xmlFree(mirrorStr);
    }

    double transparency;
    xmlChar* transparencyStr = xmlGetProp(cur, (xmlChar*)"transparency");
    if(transparencyStr == NULL){
        cout << "Dielectric shader element missing 'transparency' attribute\n";
        xmlFree(transparencyStr);
        return NULL;
    }
    else{
        transparency = atof((char*)transparencyStr);
        xmlFree(transparencyStr);
    }

    double refractiveIndex;
    xmlChar* indexStr = xmlGetProp(cur, (xmlChar*)"refractiveIndex");
    if(indexStr == NULL){
        cout << "Dielectric shader element missing 'refractiveIndex' attribute\n";
        xmlFree(indexStr);
        return NULL;
    }
    else{
        refractiveIndex = atof((char*)indexStr);
        xmlFree(indexStr);
    }


    cur = cur->children;
    while(cur != NULL){
        if(nodeNameEquals(cur, "color")) {
            specularColor = parseVector(doc, cur);
            foundSpecular = true;
        }
        cur = cur->next;
    }
    if(!foundSpecular){
        cout << "Dielectric shader element missing specular color\n";
        return NULL;
    }

    DielectricShader* dielectricShader = new DielectricShader();
    dielectricShader->setSpecularColor(specularColor);
    dielectricShader->setReflectivity(mirrorVal);
    dielectricShader->setPhongExponent(phongExponent);
    dielectricShader->setTransparency(transparency);
    dielectricShader->setRefractiveIndex(refractiveIndex);
    cur = start;
    return dielectricShader;
}

Texture* XMLSceneBuilder::parseTexture(xmlDocPtr doc, xmlNodePtr cur){
    Texture* texture = NULL;
    
    xmlChar* refId = xmlGetProp(cur, (xmlChar*)"ref");
    if(refId != NULL)
    {
        texture = getIndexedTexture((char*)refId);
        if(texture != NULL){
            xmlFree(refId);
            return texture;
        }
        
        texture = parseTexture(doc, getNodeById(doc, xmlDocGetRootElement(doc), refId));
        xmlFree(refId);
        return texture;
    }
    xmlFree(refId);

    xmlChar* textureType = xmlGetProp(cur, (xmlChar*)"type");
    if(textureType == NULL){
        cout << "Texture element missing 'type' property\n";
        return NULL;
    }
    else if(xmlStrcmp(textureType, (xmlChar*)"flat") == 0){
        xmlFree(textureType);
        texture = parseFlatTexture(doc, cur);
    }
    else if(xmlStrcmp(textureType, (xmlChar*)"checker") == 0){
        xmlFree(textureType);
        texture = parseCheckerTexture(doc, cur);
    }
    else if(xmlStrcmp(textureType, (xmlChar*)"mapped") == 0){
        xmlFree(textureType);
        texture = parseMappedTexture(doc, cur);
    }
    else cout << "Invalid texture type: '" << (char*)textureType << "'\n";
    xmlFree(textureType);
    
    if(texture != NULL){
        xmlChar* nodeId = xmlGetProp(cur, (xmlChar*)"id");
        if(nodeId != NULL && getIndexedTexture((char*)nodeId) == NULL){
            stringstream ss;
            ss << (char*)nodeId;
            texture->name = ss.str();
            namedTextures.push_back(texture);
        }
        xmlFree(nodeId);
    }
    
    return texture;
}

FlatTexture* XMLSceneBuilder::parseFlatTexture(xmlDocPtr doc, xmlNodePtr cur){
    Vector3D color(0,0,0);
    bool foundColor = false;
    xmlNodePtr start = cur;

    cur = cur->children;
    while(cur != NULL){
        if(nodeNameEquals(cur, "color")){
            color = parseVector(doc, cur);
            foundColor = true;
        }
        cur = cur->next;
    }

    if(!foundColor){
        cout << "Flat texture element missing color\n";
        return NULL;
    }

    cur = start;
    return new FlatTexture(color);
}

MappedTexture* XMLSceneBuilder::parseMappedTexture(xmlDocPtr doc, xmlNodePtr cur){
    xmlNodePtr start = cur;

    MappedTexture* tex = NULL;

    xmlChar* srcstr = xmlGetProp(cur, (xmlChar*)"src");
    if(srcstr != NULL)
    {
        tex = new MappedTexture((char*)srcstr);
    }
    xmlFree(srcstr);

    cur = start;
    return tex;
}

CheckerTexture* XMLSceneBuilder::parseCheckerTexture(xmlDocPtr doc, xmlNodePtr cur){
    Vector3D color1(0,0,0);
    Vector3D color2(0,0,0);
    bool foundColor1 = false;
    bool foundColor2 = false;
    double checkSize = 1;
    xmlNodePtr start = cur;
    
    xmlChar* checkSizeStr = xmlGetProp(cur, (xmlChar*)"checkerSize");
    if(checkSizeStr != NULL)
    {
        checkSize = atof((char*)checkSizeStr);
    }
    xmlFree(checkSizeStr);
    

    cur = cur->children;
    while(cur != NULL){
        if(nodeNameEquals(cur, "color")){
            if(!foundColor1){
                color1 = parseVector(doc, cur);
                foundColor1 = true;
            }
            else if(!foundColor2){
                color2 = parseVector(doc, cur);
                foundColor2 = true;
            }
            else break;
        }
        cur = cur->next;
    }
    
    

    if(!foundColor1 || !foundColor2){
        cout << "Checker texture element missing one or more colors\n";
        return NULL;
    }

    cur = start;
    return new CheckerTexture(color1, color2, checkSize);
}

xmlNodePtr XMLSceneBuilder::getRefNode(xmlDocPtr doc, xmlNodePtr cur){
    xmlChar* refId = xmlGetProp(cur, (xmlChar*)"ref");
    xmlNodePtr refNode = NULL;

    if(refId == NULL){
        return NULL;
    }
    refNode = getNodeById(doc, xmlDocGetRootElement(doc), refId);
    if(refNode == NULL){
        cout << "Reference to element '"<< (char*)refId <<"' not found";
        xmlFree(refId);
        return NULL;
    }
    xmlFree(refId);
    return refNode;
}

xmlNodePtr XMLSceneBuilder::getNodeById(xmlDocPtr doc, xmlNodePtr start, xmlChar* id){
    if(start == NULL){
        //this node is null
        return NULL;
    }
    xmlNodePtr refNode = NULL;
    
    //this node is valid, see if it has an id
    xmlChar* nodeId = xmlGetProp(start, (xmlChar*)"id");
    if(nodeId != NULL){
        //it has an id, see if it matches
        if(xmlStrcmp(nodeId, id) == 0){
            //this node is the one we want
            refNode = start;
        }
    }
    xmlFree(nodeId);
    //if this isn't it, check this node's children
    if(refNode == NULL) refNode = getNodeById(doc, start->children, id);
    
    //if the children don't have it, check the next node
    if(refNode == NULL) refNode = getNodeById(doc, start->next, id);
    
    if(refNode == NULL){
        //give up. It's not here
        return NULL;
    }
    else return refNode;
}

Vector3D XMLSceneBuilder::parseVector(xmlDocPtr doc, xmlNodePtr cur){

    xmlChar* refId = xmlGetProp(cur, (xmlChar*)"ref");
    if(refId != NULL)
    {
        Vector3D vector = parseVector(doc, getNodeById(doc, xmlDocGetRootElement(doc), refId));
        xmlFree(refId);
        return vector;
    }
    xmlFree(refId);

    const xmlChar* vectorType = cur->name;
    double x,y,z;
    xmlChar* xstr;
    xmlChar* ystr;
    xmlChar* zstr;
    if(xmlStrcmp(vectorType, (xmlChar*) "color") == 0){
        xstr = xmlGetProp(cur, (xmlChar*)"r");
        ystr = xmlGetProp(cur, (xmlChar*)"g");
        zstr = xmlGetProp(cur, (xmlChar*)"b");
    }
    else{
        xstr = xmlGetProp(cur, (xmlChar*)"x");
        ystr = xmlGetProp(cur, (xmlChar*)"y");
        zstr = xmlGetProp(cur, (xmlChar*)"z");
    }
    if(xstr == NULL || ystr == NULL || zstr == NULL){
        cout << "Vector element '"<<(char*)vectorType <<"' missing one or more data properties\n";
        return Vector3D(0,0,0);
    }
    x = atof((char*)xstr);
    y = atof((char*)ystr);
    z = atof((char*)zstr);
    return Vector3D(x,y,z);
}

bool XMLSceneBuilder::nodeNameEquals(xmlNodePtr cur, const char* type){
    return (xmlStrcmp(cur->name, (const xmlChar*)type) == 0);
}

Shape* XMLSceneBuilder::getIndexedShape(const char* key){
    for (int i = 0; i<namedShapes.size(); i++){
        if(strcmp(key, namedShapes[i]->name.c_str()) == 0){
            return namedShapes[i];
        }
    }
    return NULL;
}

Shader* XMLSceneBuilder::getIndexedShader(const char* key){
    for (int i = 0; i<namedShaders.size(); i++){
        if(strcmp(key, namedShaders[i]->name.c_str()) == 0){
            return namedShaders[i];
        }
    }
    return NULL;    
}

Texture* XMLSceneBuilder::getIndexedTexture(const char* key){
    for (int i = 0; i<namedTextures.size(); i++){
        if(strcmp(key, namedTextures[i]->name.c_str()) == 0){
            return namedTextures[i];
        }
    }
    return NULL;    
}